﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;
using Moq;
using System.Linq.Expressions;

namespace MoqEf
{
    public class MoqContainer<TT> where TT : DbContext
    {
        public TT Context { get { return ContextMock.Object; } }
        private Mock<TT> ContextMock;
        private ICollection<Mock> DbSetMocks;
        public MoqContainer(TT context)
        {
            ContextMock = new Mock<TT>();
            DbSetMocks = new List<Mock>();
        }
                    
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection"></param>
        /// <param name="expr">the property of the DbContext that the fixtureCollection belongs to</param>
        /// <returns></returns>
        public MoqContainer<TT> AddMockDbSet<T>(ICollection<T> fixtureCollection, Expression<Func<TT, DbSet<T>>> expr) where T : class
        {
            var data = fixtureCollection.AsQueryable();
            var mockSet = new Mock<DbSet<T>>(); 
            mockSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(data.Provider); 
            mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(data.Expression); 
            mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(data.ElementType); 
            mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator()); 
            ContextMock.Setup<DbSet<T>>(expr).Returns(mockSet.Object); 
            DbSetMocks.Add(mockSet);
            //fixtureCollection is the backing store for the data, delegate the Add method to the collection's Add method.
            mockSet.Setup(m => m.Add(It.IsAny<T>())).Callback(new Action<T>(i => fixtureCollection.Add(i)));
            return this;
        }

        public Mock<TT> GetContextMock()
        {
            return this.ContextMock;
        }

        public MoqContainer<TT> VerifyAddWasCalled<T>() where T : class
        {
            this.DbSetMocks.OfType<Mock<DbSet<T>>>().ToList().ForEach(x => x.Verify(y => y.Add(It.IsAny<T>())));
            return this;
        }

        public MoqContainer<TT> VerifySaveChangesWasCalled()
        {
            this.ContextMock.Verify(y => y.SaveChanges());
            return this;
        }
    }
}
